//
// Copyright (C) 2002-2004 NVIDIA 
// 
// File: cgfxAttrDef.cpp
//
// Author: Jim Atkinson
//
// cgfxAttrDef holds the "definition" of an attribute on a cgfxShader
// node.  This definition includes all the Maya attributes plus the
// CGeffect parameter index.
//
// Changes:
//  12/2003  Kurt Harriman - www.octopusgraphics.com +1-415-893-1023
//           - Shader parameter descriptions can be queried via the
//             "-des/description" flag of cgfxShader command, together
//             with "-lp/listParameters" or "-p/parameter <name>"
//           - "-ci/caseInsensitive" option for "-p/parameter <name>"
//           - "uimin"/"uimax" annotations set numeric slider bounds.
//           - "uiname" annotation is used as parameter description 
//             if there is no "desc" annotation.
//           - Attribute bounds and initial values are updated when
//             effect is changed or reloaded.  
//           - When creating dynamic attributes for shader parameters,
//             make them keyable if type is bool, int, float or color.
//             Vector types other than colors are made keyable if
//             they have a "desc" or "uiname" annotation.
//           - Dangling references to deleted dynamic attributes
//             caused exceptions in MObject destructor, terminating
//             the Maya process.  This has been fixed.
//           - Fixed some undo/redo bugs that caused crashes and
//             incorrect rendering.  Fixed some memory leaks.
//           - Improved error handling.  Use M_CHECK for internal errors. 
//
//-
// ==========================================================================
// Copyright 1995,2006,2008 Autodesk, Inc. All rights reserved.
//
// Use of this software is subject to the terms of the Autodesk
// license agreement provided at the time of installation or download,
// or which otherwise accompanies this software in either electronic
// or hard copy form.
// ==========================================================================
//+
#include "cgfxShaderCommon.h"

#include <float.h>                     // FLT_MAX
#include <limits.h>                    // INT_MAX, INT_MIN
#include <string.h>

#include <maya/MIOStream.h>
#include <maya/MGlobal.h>
#include <maya/MString.h>
#include <maya/MStringArray.h>
#include <maya/MFnStringData.h>
#include <maya/MFnStringArrayData.h>
#include <maya/MFnDependencyNode.h>
#include <maya/MFnCompoundAttribute.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MFnMatrixAttribute.h>
#include <maya/MDGModifier.h>
#include <maya/MFnMatrixData.h>
#include <maya/MPlugArray.h>
#include <maya/MItDependencyGraph.h>
#include <maya/MSelectionList.h>

#include "cgfxAttrDef.h"
#include "cgfxShaderNode.h"
#include "cgfxFindImage.h"


//
// Defines
//

#define N_MAX_STRING_LENGTH 1024


#ifdef _WIN32

#else
#	define stricmp  strcasecmp
#	define strnicmp strncasecmp
#endif


// Constructor
cgfxAttrDef::cgfxAttrDef(CGparameter cgParameter)
: fType( kAttrTypeUnknown )
, fSize( 0 )
, fHint( kVectorHintNone )
, fNumericMin( NULL )
, fNumericMax( NULL )
, fNumericSoftMin( NULL )
, fNumericSoftMax( NULL )
, fUnits( MDistance::kInvalid )
, fNumericDef( NULL )
, fTextureId(0 )
, fTextureMonitor(kNullCallback)
// , fParameterIndex( (LPCSTR)(-1) )
, fParameterHandle(0)
, fInvertMatrix( false )
, fTransposeMatrix( false )
, fTweaked( false )
, fInitOnUndo( false )
{
    fName = cgGetParameterName(cgParameter);
    fType = cgfxAttrDef::kAttrTypeOther;
    fSize = cgGetParameterRows(cgParameter) * cgGetParameterColumns(cgParameter);
    fParameterHandle = cgParameter;
    fSemantic = cgGetParameterSemantic(cgParameter);

    CGtype cgParameterType = cgGetParameterType(cgParameter);
    switch (cgParameterType)
    {
        case CG_BOOL	: fType = cgfxAttrDef::kAttrTypeBool; break ;
		case CG_INT		: fType = cgfxAttrDef::kAttrTypeInt; break ;
		case CG_HALF	:
		case CG_FLOAT :
#ifdef _WIN32
            if (stricmp(fSemantic.asChar() , "Time")==0)
                fType = cgfxAttrDef::kAttrTypeTime;
            else 
#endif
                fType = cgfxAttrDef::kAttrTypeFloat;
            break;
		case CG_HALF2				:
		case CG_FLOAT2			: 
            fType = cgfxAttrDef::kAttrTypeVector2; 
            break ;
		case CG_HALF3				:
		case CG_FLOAT3			: 
            fType = cgfxAttrDef::kAttrTypeVector3; 
            break ;
		case CG_HALF4				:
		case CG_FLOAT4			: 
            fType = cgfxAttrDef::kAttrTypeVector4; 
            break ;
		case CG_HALF4x4			:
		case CG_FLOAT4x4		: 
            setMatrixType(cgParameter); 
            break ;
		case CG_STRING			: 
            fType = cgfxAttrDef::kAttrTypeString; 
            break ;
		case CG_TEXTURE			:
            // handled by setSamplerType()
            break ;
		case CG_SAMPLER1D		: 
		case CG_SAMPLER2D		:
		case CG_SAMPLER3D		:
		case CG_SAMPLERRECT :
		case CG_SAMPLERCUBE :
            setSamplerType(cgParameter);
            break ;
		case CG_ARRAY				:
		case CG_STRUCT			:
            break ;

		default							: 
            MString msg = cgGetTypeString(cgParameterType);
            msg += " not yet supported";
            MGlobal::displayError(msg);
            M_CHECK( false );
    }

    if (fType == cgfxAttrDef::kAttrTypeVector3 ||
        fType == cgfxAttrDef::kAttrTypeVector4)
    {
        // Set the specific vector type
        //
        setVectorType(cgParameter);
    }

    // Now that we know something about this attribute, walk through
    // the annotations on the parameter and see if there is any
    // additional information we can find.
    //

    MString	sUIName;
        
    CGannotation cgAnnotation = cgGetFirstParameterAnnotation(cgParameter);
    while (cgAnnotation)
    {
        const char* annotationName		= cgGetAnnotationName(cgAnnotation);
        const char* annotationValue		= cgGetStringAnnotationValue(cgAnnotation);
        CGtype			cgAnnotationType	= cgGetAnnotationType(cgAnnotation);

        if (stricmp(annotationName, "uihelp") == 0)
        {
            fDescription = MString(annotationValue);
        }
        else if (stricmp(annotationName, "uiname" ) == 0 )
        {
            sUIName = MString(annotationValue);
        }
        else if( stricmp( annotationName, "units") == 0)
        {
            if( stricmp( annotationValue, "inches") == 0)
            {
                fUnits = MDistance::kInches;
            }
            else if( stricmp( annotationValue, "millimetres") == 0 || stricmp( annotationValue, "millimeters") == 0 || stricmp( annotationValue, "mm") == 0)
            {
                fUnits = MDistance::kMillimeters;
            }
            else if( stricmp( annotationValue, "centimetres") == 0 || stricmp( annotationValue, "centimeters") == 0 || stricmp( annotationValue, "cm") == 0)
            {
                fUnits = MDistance::kCentimeters;
            }
            else if( stricmp( annotationValue, "metres") == 0 || stricmp( annotationValue, "meters") == 0 || stricmp( annotationValue, "m") == 0)
            {
                fUnits = MDistance::kMeters;
            }
            else if( stricmp( annotationValue, "kilometres") == 0 || stricmp( annotationValue, "kilometers") == 0 || stricmp( annotationValue, "km") == 0)
            {
                fUnits = MDistance::kKilometers;
            }
            else if( stricmp( annotationValue, "feet") == 0)
            {
                fUnits = MDistance::kFeet;
            }
        }
        else if ((fType >= cgfxAttrDef::kAttrTypeFirstTexture &&
                  fType <= cgfxAttrDef::kAttrTypeLastTexture))
        {
            if (stricmp(annotationName, "resourcetype") == 0)
            {
                if (stricmp(annotationValue, "1d") == 0)
                {
                    fType = cgfxAttrDef::kAttrTypeColor1DTexture;
                }
                else if (stricmp(annotationValue, "2d") == 0)
                {
                    fType = cgfxAttrDef::kAttrTypeColor2DTexture;
                }
                else if (stricmp(annotationValue, "3d") == 0)
                {
                    fType = cgfxAttrDef::kAttrTypeColor3DTexture;
                }
                else if (stricmp(annotationValue, "cube") == 0)
                {
                    fType = cgfxAttrDef::kAttrTypeCubeTexture;
                }
                else if (stricmp(annotationValue, "rect") == 0)
                {
                    fType = cgfxAttrDef::kAttrTypeColor2DRectTexture;
                }
            }
            else if (stricmp(annotationName, "resourcename") == 0)
            {
                // Store the texture file to load as the
                // string default argument.  (I know, its kind
                // of a kludge; but if the texture attributes
                // were string values, it would be exactly
                // correct.
                //
                fStringDef = annotationValue;
            }
        }
        else if (cgAnnotationType == CG_BOOL )
        {}                     // no min/max for bool
        else if (stricmp(annotationName, "min") == 0 ||
                 stricmp(annotationName, "max") == 0 ||
                 stricmp(annotationName, "uimin") == 0 ||
                 stricmp(annotationName, "uimax") == 0 )
        {
            double * tmp = new double [fSize];

            switch (cgAnnotationType)
            {
				case CG_INT:
                    {
                        // int* val = (int*) adesc.Value;
                        // for (int k = 0; k < fSize; ++k)
                        // {
                        //     tmp[k] = val[k];
                        // }
                        
                        int nValues;
                        const int* annotationValues = cgGetIntAnnotationValues(cgAnnotation, &nValues);
                        
                        for (int iValue = 0; iValue < nValues; ++iValue)
                            tmp[iValue] = static_cast<double>(annotationValues[iValue]);
                    }   
                    break;

				case CG_FLOAT:
                    {
                        // float* val = (float*) adesc.Value;
                        // for (int k = 0; k < fSize; ++k)
                        // {
                        //     tmp[k] = val[k];
                        // }
                        int nValues;
                        const float* annotationValues = cgGetFloatAnnotationValues(cgAnnotation, &nValues);

                        for (int iValue = 0; iValue < nValues; ++iValue)
                            tmp[iValue] = static_cast<double>(annotationValues[iValue]);
                    }
                    break;

				default:
                    // This is not a numeric attribute, reset tmp to NULL
                    delete [] tmp;
                    tmp = 0;
                    break;
            }

            if (stricmp(annotationName, "min") == 0)
            {
                fNumericMin = tmp;
            }
            else if (stricmp(annotationName, "max") == 0)
            {
                fNumericMax = tmp;
            }
            else if (stricmp(annotationName, "uimin") == 0)
            {
                fNumericSoftMin = tmp;
            }
            else 
            {
                fNumericSoftMax = tmp;
            }
        } // end of if (adesc.Name == "min"|"max")

        cgAnnotation = cgGetNextAnnotation(cgAnnotation);
    }

    // Enforce limits on colors if they do not already have them.
    if ( fType == cgfxAttrDef::kAttrTypeColor3 ||
         fType == cgfxAttrDef::kAttrTypeColor4 )
    {
        if ( !fNumericMin )
        {
            fNumericMin = new double[4];
            fNumericMin[0] = 0.0;
            fNumericMin[1] = 0.0;
            fNumericMin[2] = 0.0;
            fNumericMin[3] = 0.0;
        }
        if ( !fNumericMax )
        {
            fNumericMax = new double[4];
            fNumericMax[0] = 1.0;
            fNumericMax[1] = 1.0;
            fNumericMax[2] = 1.0;
            fNumericMax[3] = 1.0;
        }
    }

    // If no description, use UIName.
    if ( !fDescription.length() )
        fDescription = sUIName;

    // Now get the default values
    //
    double* tmp = new double [fSize];

    CGtype cgParameterBaseType = cgGetParameterBaseType(cgParameter);

    switch (cgParameterBaseType)
    {
		case CG_BOOL:
            {
                int val;
                if (cgGetParameterValueic(cgParameter, 1, &val) != 1)
                {
                    delete [] tmp;
                    tmp = 0;
                    break;
                }

                for (int k = 0; k < fSize; ++k)
                {
                    tmp[k] = val ? 1 : 0;
                }
            }
            break;

		case CG_INT:
            {
                int val;
                if (cgGetParameterValueic(cgParameter, 1, &val) != 1)
                {
                    delete [] tmp;
                    tmp = 0;
                    break;
                }

                for (int k = 0; k < fSize; ++k)
                {
                    tmp[k] = val;
                }
            }
            break;

		case CG_FLOAT:
            {
                if (fSize == 1)
                {
                    float val;
                    if (cgGetParameterValuefc(cgParameter, 1, &val) != 1)
                    {
                        delete [] tmp;
                        tmp = 0;
                        break;
                    }

                    tmp[0] = val;
                }
                else if (fSize <= 4 || fType == cgfxAttrDef::kAttrTypeMatrix)
                {
                    float val[16];
                    if (fType == kAttrTypeMatrix)
                    {
                        cgGetMatrixParameterfc(cgParameter, val);
                    }
                    else
                    {
                        unsigned int vecSize = fSize;
                        cgGetParameterValuefc(cgParameter, vecSize, val);
                    }
                    /*if (result != S_OK)
                      {
                      delete [] tmp;
                      tmp = 0;
                      break;
                      }*/

                    for (int k = 0; k < fSize; ++k)
                    {
                        tmp[k] = val[k];
                    }
                }
            }
            break;

        case CG_STRING:
            {
#ifdef _WIN32
                LPCSTR val;
#else
                const char* val = NULL;
#endif
                val = cgGetStringParameterValue(cgParameter);

                fStringDef = val;
            }
            // Fall through into the default case to destroy the
            // numeric default value.
            //

		default:
            // We don't know what to do but there is no point in
            // keeping tmp around.
            //
            delete [] tmp;
            tmp = 0;
    }

    // Don't save initial value if it is zero (or identity matrix).
    if ( tmp )
    {
        int k;
        if ( fSize == 16 )
        {
            const double* d = &MMatrix::identity[0][0];
            for ( k = 0; k < fSize; ++k )
                if ( tmp[ k ] != d[ k ] )
                    break;
        }
        else
        {
            for ( k = 0; k < fSize; ++k )
                if ( tmp[ k ] != 0.0 )
                    break;
        }
        if ( k == fSize )
        {
            delete [] tmp;
            tmp = 0;
        }
    }

    fNumericDef = tmp;

};


cgfxAttrDef::cgfxAttrDef(
    const MString&          sAttrName,
    const cgfxAttrType      eAttrType,
    const MString&          sDescription,
    const MString&          sSemantic,
    MObject                 obNode,
    MObject                 obAttr
)
: fType( kAttrTypeUnknown )
, fSize( 0 )
, fHint( kVectorHintNone )
, fNumericMin( NULL )
, fNumericMax( NULL )
, fNumericSoftMin( NULL )
, fNumericSoftMax( NULL )
, fUnits( MDistance::kInvalid )
, fNumericDef( NULL )
, fTextureId(0 )
, fTextureMonitor(kNullCallback)
// , fParameterIndex( (LPCSTR)(-1) )
, fParameterHandle(0)
, fInvertMatrix( false )
, fTransposeMatrix( false )
, fTweaked( false )
, fInitOnUndo( false )
{
    MStatus status;
    
    fName = sAttrName;
    fType = eAttrType;
    fDescription = sDescription;
    fSemantic = sSemantic;
    fAttr = obAttr;

    MFnCompoundAttribute fnCompound;
    MFnNumericAttribute  fnNumeric;
    MFnTypedAttribute    fnTyped;
    MFnMatrixAttribute   fnMatrix;

    MPlug   plug( obNode, obAttr );
    double  numericMin[4];
    double  numericMax[4];
    double  numericValue[4];
    bool    hasMin    = false;
    bool    hasMax    = false; 
    bool    isNumeric = false;

    // If compound attribute, get value and bounds of each element.
    if ( fnCompound.setObject( obAttr ) )
    {
        hasMin = true;
        hasMax = true;
        isNumeric = true;

        MObject      obChild;
        MStringArray saChild;
        int iChild;
        int nChild = fnCompound.numChildren();
        M_CHECK( nChild >= 2 && nChild <= 3 );
        for ( iChild = 0; iChild < nChild; ++iChild )
        {
            // Get child attribute.
            if ( iChild < 3 )
            {
                obChild = fnCompound.child( iChild, &status );
                M_CHECK( status );
            }

            status = fnNumeric.setObject( obChild );
            M_CHECK( status );

            // Min
            if ( fnNumeric.hasMin() )
                fnNumeric.getMin( numericMin[ iChild ] );
            else
                hasMin = false; 

            // Max
            if ( fnNumeric.hasMax() )
                fnNumeric.getMax( numericMax[ iChild ] );
            else
                hasMax = false; 

            // Value
            MPlug plChild( obNode, obChild );
            status = plChild.getValue( numericValue[ iChild ] );
            M_CHECK( status );

            // Check for 4-element vector.
            saChild.append( fnNumeric.name() );
            if ( iChild == 2 )
            {
                const char* suffix = NULL;
                if ( saChild[0] == sAttrName + "X" &&
                     saChild[1] == sAttrName + "Y" &&
                     saChild[2] == sAttrName + "Z" ) 
                    suffix = "W";
                else if ( saChild[0] == sAttrName + "R" &&
                          saChild[1] == sAttrName + "G" &&
                          saChild[2] == sAttrName + "B" ) 
                    suffix = "Alpha";
                if ( suffix )
                {
                    MString sName2 = sAttrName + suffix;
                    MFnDependencyNode fnNode(obNode);
                    obChild = fnNode.attribute( sName2, &status );
                    MFnNumericData::Type ndt = fnNumeric.unitType();
                    if ( status &&
                         fnNumeric.setObject( obChild ) &&
                         fnNumeric.unitType() == ndt )
                    {
                        fAttr2 = obChild;
                        nChild = 4;     // loop again to get extra attr
                    }
                }
            }
        }                          // loop over children
        fSize = nChild;
    }                              // compound 

    // Simple numeric attribute?  
    else if ( fnNumeric.setObject( obAttr ) )
    {
        MFnNumericAttribute fnNumeric( obAttr, &status );
        M_CHECK( status );

	

        fSize = 1;
        isNumeric = true;

        // Get min and max.
        if ( fnNumeric.hasMin() )
        {
            fnNumeric.getMin( numericMin[0] );
            hasMin = true;
        }
        if ( fnNumeric.hasMax() )
        {
            fnNumeric.getMax( numericMax[0] );
            hasMax = true;
        }

        // Get slider bounds.
        if ( fnNumeric.hasSoftMin() )
        {
            fNumericSoftMin = new double[1];
            fnNumeric.getSoftMin( fNumericSoftMin[0] );
        }
        if ( fnNumeric.hasSoftMax() )
        {
            fNumericSoftMax = new double[1];
            fnNumeric.getSoftMax( fNumericSoftMax[0] );
        }

        // Value 
        status = plug.getValue( numericValue[0] );
        M_CHECK( status );
    }                              // simple numeric

    // String attribute?
    else if (fnTyped.setObject( obAttr ) &&
             fnTyped.attrType() == MFnData::kString)
    {
        fSize = 1;
        status = plug.getValue( fStringDef );
        M_CHECK( status );
    }                           // string
    // Matrix attribute?
    else if (fnMatrix.setObject( obAttr ))
    {
        MObject obData;
        status = plug.getValue( obData );
        M_CHECK( status );
        MFnMatrixData fnMatrixData( obData, &status );
        M_CHECK( status );
        const MMatrix& mat = fnMatrixData.matrix( &status );
        M_CHECK( status );
        fSize = 16;
        fNumericDef = new double[ fSize ];
        const double* p = &mat.matrix[0][0];
        for ( int i = 0; i < 16; ++i )
            fNumericDef[ i ] = p[ i ];
        M_CHECK( status );
    }                           // matrix 
    // Mystified...
    else
        M_CHECK( false );
    

    // Store numeric value, min and max.
    if ( isNumeric )
    {
        fNumericDef = new double[ fSize ];
        memcpy( fNumericDef, numericValue, fSize * sizeof( numericValue[0] ) );
        if ( hasMin )
        {
            fNumericMin = new double[ fSize ];
            memcpy( fNumericMin, numericMin, fSize * sizeof( numericMin[0] ) );
        }
        if ( hasMax )
        {
            fNumericMax = new double[ fSize ];
            memcpy( fNumericMax, numericMax, fSize * sizeof( numericMax[0] ) );
        }
    }

    // set attribute flags
    setAttributeFlags();
}

// Destructor
cgfxAttrDef::~cgfxAttrDef()
{
	release();
	delete [] fNumericMin;
	delete [] fNumericMax;
	delete [] fNumericSoftMin;
	delete [] fNumericSoftMax;
	delete [] fNumericDef;
};


// Release any associated resources
void cgfxAttrDef::release()
{
	releaseTexture();
	releaseCallback();
}
void cgfxAttrDef::releaseTexture()
{
	if( fTextureId != 0 )
	{
		glDeleteTextures( 1, &fTextureId);
		fTextureId = 0;
	}
}
void cgfxAttrDef::releaseCallback()
{
	if( fTextureMonitor != kNullCallback)
	{
		MMessage::removeCallback( fTextureMonitor);
		fTextureMonitor = kNullCallback;
	}
}


// ================ cgfxAttrDef::setTextureType ================

// This method looks at the parameter data type, semantic, and
// annotation and determines
void cgfxAttrDef::setTextureType(CGparameter cgParameter)
{
	fType = kAttrTypeColor2DTexture;

	const char* semantic = cgGetParameterSemantic(cgParameter);

	if (!semantic) return;

	// We have to go thru semantics and annotations to find the type of the texture

	if (semantic)
	{
		if (stricmp(semantic, "normal") == 0)
		{
			fType = kAttrTypeNormalTexture;
		}
		else if (stricmp(semantic, "height") == 0)
		{
			fType = kAttrTypeBumpTexture;
		}
		else if (stricmp(semantic, "environment") == 0)
		{
			fType = kAttrTypeEnvTexture;
		}
		else if (stricmp(semantic, "environmentnormal") == 0)
		{
			fType = kAttrTypeNormalizationTexture;
		}
	}

	// Now browse through the annotations to see if there is anything
	// interesting there too.

	CGannotation cgAnnotation = cgGetFirstParameterAnnotation(cgParameter);
	while (cgAnnotation)
	{
		const char*	annotationName	= cgGetAnnotationName(cgAnnotation);
		const char* annotationValue = cgGetStringAnnotationValue(cgAnnotation);
		
		if (stricmp(annotationName, "resourcetype") == 0)
		{
			if (stricmp(annotationValue, "1d") == 0)
			{
				fType = kAttrTypeColor1DTexture;
			}
			else if (stricmp(annotationValue, "2d") == 0)
			{
				fType = kAttrTypeColor2DTexture;
			}
			else if (stricmp(annotationValue, "rect") == 0)
			{
				fType = kAttrTypeColor2DRectTexture;
			}
			else if (stricmp(annotationValue, "3d") == 0)
			{
				fType = kAttrTypeColor3DTexture;
			}
			else if (stricmp(annotationValue, "cube") == 0)
			{
				fType = kAttrTypeCubeTexture;
			}
		}
		else if (stricmp(annotationName, "resourcename") == 0)
		{
			// Store the texture file to load as the
			// string default argument.  (I know, its kind
			// of a kludge; but if the texture attributes
			// were string values, it would be exactly
			// correct.
			//
			fStringDef = annotationValue;
		}

		cgAnnotation = cgGetNextAnnotation(cgAnnotation);
	}
}

void cgfxAttrDef::setSamplerType(CGparameter cgParameter)
{
	CGstateassignment cgStateAssignment = cgGetNamedSamplerStateAssignment(cgParameter, "texture");
	setTextureType(cgGetTextureStateAssignmentValue(cgStateAssignment));
}

void cgfxAttrDef::setMatrixType(CGparameter cgParameter)
{
	fType = kAttrTypeMatrix;

	const char* semantic = cgGetParameterSemantic(cgParameter);

	if (!semantic)
		return;

	if (stricmp(semantic, "world") == 0)
	{
		fType = kAttrTypeWorldMatrix;
		fInvertMatrix = false;
		fTransposeMatrix = false;
	}
	else if (stricmp(semantic, "worldinverse") == 0)
	{
		fType = kAttrTypeWorldMatrix;
		fInvertMatrix = true;
		fTransposeMatrix = false;
	}
	else if (stricmp(semantic, "worldtranspose") == 0)
	{
		fType = kAttrTypeWorldMatrix;
		fInvertMatrix = false;
		fTransposeMatrix = true;
	}
	else if (stricmp(semantic, "worldinversetranspose") == 0)
	{
		fType = kAttrTypeWorldMatrix;
		fInvertMatrix = true;
		fTransposeMatrix = true;
	}
	else if (stricmp(semantic, "worldview") == 0)
	{
		fType = kAttrTypeWorldViewMatrix;
		fInvertMatrix = false;
		fTransposeMatrix = false;
	}
	else if (stricmp(semantic, "worldviewtranspose") == 0)
	{
		fType = kAttrTypeWorldViewMatrix;
		fInvertMatrix = false;
		fTransposeMatrix = true;
	}
	else if (stricmp(semantic, "worldviewinverse") == 0)
	{
		fType = kAttrTypeWorldViewMatrix;
		fInvertMatrix = true;
		fTransposeMatrix = false;
	}
	else if (stricmp(semantic, "worldviewinversetranspose") == 0)
	{
		fType = kAttrTypeWorldViewMatrix;
		fInvertMatrix = true;
		fTransposeMatrix = true;
	}
	else if (stricmp(semantic, "worldviewprojection") == 0)
	{
		fType = kAttrTypeWorldViewProjectionMatrix;
		fInvertMatrix = false;
		fTransposeMatrix = false;
	}
	else if (stricmp(semantic, "worldviewprojectiontranspose") == 0)
	{
		fType = kAttrTypeWorldViewProjectionMatrix;
		fInvertMatrix = false;
		fTransposeMatrix = true;
	}
	else if (stricmp(semantic, "worldviewprojectioninverse") == 0)
	{
		fType = kAttrTypeWorldViewProjectionMatrix;
		fInvertMatrix = true;
		fTransposeMatrix = false;
	}
	else if (stricmp(semantic, "worldviewprojectioninversetranspose") == 0)
	{
		fType = kAttrTypeWorldViewProjectionMatrix;
		fInvertMatrix = true;
		fTransposeMatrix = true;
	}
	else if (stricmp(semantic, "view") == 0)
	{
		fType = kAttrTypeViewMatrix;
		fInvertMatrix = false;
		fTransposeMatrix = false;
	}
	else if (stricmp(semantic, "viewinverse") == 0)
	{
		fType = kAttrTypeViewMatrix;
		fInvertMatrix = true;
		fTransposeMatrix = false;
	}
	else if (stricmp(semantic, "viewtranspose") == 0)
	{
		fType = kAttrTypeViewMatrix;
		fInvertMatrix = false;
		fTransposeMatrix = true;
	}
	else if (stricmp(semantic, "viewinversetranspose") == 0)
	{
		fType = kAttrTypeViewMatrix;
		fInvertMatrix = true;
		fTransposeMatrix = true;
	}
	else if (stricmp(semantic, "projection") == 0)
	{
		fType = kAttrTypeProjectionMatrix;
		fInvertMatrix = false;
		fTransposeMatrix = false;
	}
	else if (stricmp(semantic, "projectioninverse") == 0)
	{
		fType = kAttrTypeProjectionMatrix;
		fInvertMatrix = true;
		fTransposeMatrix = false;
	}
	else if (stricmp(semantic, "projectiontranspose") == 0)
	{
		fType = kAttrTypeProjectionMatrix;
		fInvertMatrix = false;
		fTransposeMatrix = true;
	}
	else if (stricmp(semantic, "projectioninversetranspose") == 0)
	{
		fType = kAttrTypeProjectionMatrix;
		fInvertMatrix = true;
		fTransposeMatrix = true;
	}
}

// This routine returns true if the semantic value is known to refer
// to a color value instead of a positional value.  We determine that
// this is a color value because it uses one of the known names or it
// contains the string "color" or "colour" in it.
//
void cgfxAttrDef::setVectorType(CGparameter cgParameter)
{
#if 0
	// This variable is not used
	static char* colorList[] = 
	{
		"diffuse",
		"specular",
		"ambient",
		"emissive",
	};
#endif

	const char* semantic = cgGetParameterSemantic(cgParameter);

	if ((semantic == NULL || strcmp(semantic,"") == 0) == false)
	{
		// Check the semantic value to see if this is a color
		//
		if (stricmp(semantic, "diffuse") == 0 ||
			stricmp(semantic, "specular") == 0 ||
			stricmp(semantic, "ambient") == 0 ||
			stricmp(semantic, "emissive") == 0)
		{
			fType = (fSize == 3) ? kAttrTypeColor3 : kAttrTypeColor4;
		}
		else if (stricmp(semantic, "direction") == 0)
		{
			fType = kAttrTypeFirstDir;
		}
		else if (stricmp(semantic, "position") == 0)
		{
			fType = kAttrTypeFirstPos;
		}
	}


	CGannotation cgAnnotation = cgGetFirstParameterAnnotation(cgParameter);
	while (cgAnnotation)
	{
		const char*	annotationName	= cgGetAnnotationName(cgAnnotation);
		const char* annotationValue = cgGetStringAnnotationValue(cgAnnotation);
		
		if (stricmp(annotationName, "type") == 0)
		{
			if (strlen(annotationValue) != 0)
			{
				if (stricmp(annotationValue, "color") == 0)
				{
					fType = (fSize == 3) ? kAttrTypeColor3 : kAttrTypeColor4;
				}
			}
		}
		else if (stricmp(annotationName, "space") == 0)
		{
			// we assume the attribute to be a direction if it doesn't have semantic to determine it.
			// display a warning at the same time
			if(fType != kAttrTypeFirstDir && fType != kAttrTypeFirstPos)
			{
				const MString warnMsg = fName + " has space annotation but doesn't have semantic to determine if it is a direction or position! Assuming it to be a direction. You'd better add a semantic for it!";
				MGlobal::displayWarning(warnMsg);
				fType = kAttrTypeFirstDir;
			}
				
			if (stricmp(annotationValue, "world") == 0)
			{
				fType = (cgfxAttrType)(fType + kAttrTypeWorldDir - kAttrTypeFirstDir);
			}
			else if (stricmp(annotationValue, "view") == 0 ||
				stricmp(annotationValue, "devicelightspace") == 0)
			{
				fType = (cgfxAttrType)(fType + kAttrTypeViewDir - kAttrTypeFirstDir);
			}
			else if (stricmp(annotationValue, "projection") == 0)
			{
				fType = (cgfxAttrType)(fType + kAttrTypeProjectionDir - kAttrTypeFirstDir);
			}
			else if (stricmp(annotationValue, "screen") == 0)
			{
				fType = (cgfxAttrType)(fType + kAttrTypeScreenDir - kAttrTypeFirstDir);
			}
		}
		else if (stricmp(annotationName, "object") == 0)
		{
			fHint = kVectorHintNone;

			if (stricmp(annotationValue, "dirlight") == 0)
			{
				fHint = kVectorHintDirLight;
			}
			else if (stricmp(annotationValue, "spotlight") == 0)
			{
				fHint = kVectorHintSpotLight;
			}
			else if (stricmp(annotationValue, "pointlight") == 0)
			{
				fHint = kVectorHintPointLight;
			}
			else if (stricmp(annotationValue, "camera") == 0 || stricmp(annotationValue, "eye") == 0)
			{
				fHint = kVectorHintEye;
			}
		}
		cgAnnotation = cgGetNextAnnotation(cgAnnotation);
	}
	return;
}

// ========== cgfxAttrDef::attrsFromNode ==========
//
// This function simply returns the parses through the dynamic
// attributes on an effect and builds a list of cgfxAttrDef objects.
// The cgfxAttrDef objects in the list are incomplete but they are
// only used to determine which attributes on the object need to be
// created, destroyed, or left alone.  Ultimately, the cgfxAttrDefList
// that is constructed from the effect itself will be the one held by
// the node.
//
/* static */
cgfxRCPtr<cgfxAttrDefList> cgfxAttrDef::attrsFromNode(MObject& oNode)
{
	MStatus status;

	MFnDependencyNode fnNode(oNode, &status);
	M_CHECK( status );
	M_CHECK( fnNode.typeId() == cgfxShaderNode::sId );

	cgfxShaderNode* pNode = (cgfxShaderNode *) fnNode.userNode();
	M_CHECK( pNode );

	cgfxRCPtr<cgfxAttrDefList> list = pNode->attrDefList();

	// The list has not been initialized.  Create it and try again.
	//
	if (list.isNull())
	{
		buildAttrDefList(oNode);
		list = pNode->attrDefList();
	}

	return list;
}

// ========== cgfxAttrDef::buildAttrDefList ==========
//
// This routine reconstructs the attrDefList from stringArray value in
// the attributeList attribute.  The reconstructed list is incomplete
// but it is good enough to compare to the list generated by
// attrsFromEffect to see if the connections are still valid.
//
void cgfxAttrDef::buildAttrDefList(MObject& oNode)
{
	MStatus status;

    MFnDependencyNode fnNode(oNode, &status);
    M_CHECK( status &&
             fnNode.typeId() == cgfxShaderNode::sId );

    cgfxShaderNode* pNode = (cgfxShaderNode *) fnNode.userNode();
    M_CHECK( pNode && pNode->attrDefList().isNull() );

    cgfxRCPtr<cgfxAttrDefList> list(new cgfxAttrDefList);

    MStringArray saList;

    pNode->getAttributeList(saList);
    //         MStatus status;

    //         // Get the value of the attributeList attribute
    //         //
    //         MPlug    plug(oNode, cgfxShaderNode::sAttributeList);

    //         MObject saDataObject;

    //         status = plug.getValue(saDataObject);
    //         if (!status)
    //         {
    //             sprintf(errorMsg, "%s(%d): failed to get attributeList value: %s!",
    //                     __FILE__, __LINE__, status.errorString().asChar());
    //             throw errorMsg;
    //         }

    //         MFnStringArrayData fnSaData(saDataObject, &status);
    //         if (!status)
    //         {
    //             sprintf(errorMsg,
    //                     "%s(%d): failed to construct attributeList function set: %s!",
    //                     __FILE__, __LINE__, status.errorString().asChar());
    //             throw errorMsg;
    //         }

    //         fnSaData.copyTo(saList);

    // Ok, we succeeded, saList is now an array of "top level"
    // dynamic attribute names along with some minimal type
    // information.  Parse through it and reconstruct the
    // cgfxAttrDefList.
    //
    unsigned int i;
    for (i = 0; i < saList.length(); ++i)
    {
        MString item = saList[i];
        MStringArray  splitItem;
        MObject oAttr;

        item.split('\t', splitItem);

        cgfxAttrDef* attrDef = attrFromNode( fnNode,
                                             splitItem[0], 
                                             (cgfxAttrType)(splitItem[1].asInt()),
                                             splitItem[2],
                                             splitItem[3]);
        if ( attrDef )
        {
            list->add( attrDef );
        }
    }

    pNode->setAttrDefList(list);
}


/* static */
cgfxAttrDef*
cgfxAttrDef::attrFromNode( const MFnDependencyNode& fnNode,
				           const MString&           sAttrName,
						   const cgfxAttrType       eAttrType,
			   			   const MString&           sDescription,
					       const MString&           sSemantic)
{
	MStatus      status;
	cgfxAttrDef* attrDef = NULL;

	try
	{
		MObject obNode = fnNode.object();
		MObject obAttr = fnNode.attribute( sAttrName, &status );
		if ( !status )                 // if node doesn't have this attr
			return NULL;               // skip it

		attrDef = new cgfxAttrDef(
            sAttrName, eAttrType, sDescription, sSemantic,
            obNode, obAttr
        );

	}
	catch ( cgfxShaderCommon::InternalError* e )   
	{
		size_t ee = (size_t)e;
		MString sMsg = "(";
		sMsg += (int)ee;
		sMsg += ") cgfxShader node \"";
		sMsg += fnNode.name();
		sMsg += "\" has invalid attribute \"";
		sMsg += sAttrName;
		sMsg += "\" - ignored";
		MGlobal::displayWarning( sMsg );
		delete attrDef;
		attrDef = NULL; 
	}
	catch (...)
	{
		delete attrDef;
		M_CHECK( false );
	}

	return attrDef;
}                                      // cgfxAttrDef::attrFromNode


bool
cgfxAttrDef::createAttribute( const MObject& oNode, MDGModifier* mod, cgfxShaderNode* pNode)
{
	MFnDependencyNode fnNode( oNode );

	// Return if node already has an attribute with the specified name.
	// (Shader var name could conflict with a predefined static attr.)
	MObject obExistingAttr = fnNode.attribute( fName );
	if ( !obExistingAttr.isNull() )
	{
		return false;
	}

	try
	{
		MStatus status;
		MObject oAttr, oAttr2;

		MFnNumericAttribute     nAttr;
		MFnTypedAttribute       tAttr;
		MFnMatrixAttribute      mAttr;

		MObject oSrcNode, oDstNode;
		MFnDependencyNode fnFile;
		MObject oSrcAttr, oDstAttr;

		bool doConnection = false;

		switch (fType)
		{
		case kAttrTypeBool:
			oAttr = nAttr.create( fName, fName, MFnNumericData::kBoolean,
				0.0, &status );
			M_CHECK( status );
			nAttr.setKeyable( true );
			nAttr.setAffectsAppearance( true );
			break;

		case kAttrTypeInt:
			oAttr = nAttr.create( fName, fName, MFnNumericData::kInt,
				0.0, &status );
			M_CHECK( status );
			nAttr.setKeyable( true );
			nAttr.setAffectsAppearance( true );
			break;

		case kAttrTypeFloat:
			oAttr = nAttr.create( fName, fName, MFnNumericData::kFloat,
				0.0, &status );
			M_CHECK( status );
			nAttr.setKeyable( true );
			nAttr.setAffectsAppearance( true );
			break;

		case kAttrTypeString:
			oAttr = tAttr.create(fName, fName, MFnData::kString,
				MObject::kNullObj, &status );
			tAttr.setAffectsAppearance( true );
			M_CHECK( status );
			break;

		case kAttrTypeVector2:
		case kAttrTypeVector3:
		case kAttrTypeVector4:
		case kAttrTypeColor3:
		case kAttrTypeColor4:

		case kAttrTypeObjectDir:
		case kAttrTypeWorldDir:
		case kAttrTypeViewDir:
		case kAttrTypeProjectionDir:
		case kAttrTypeScreenDir:

		case kAttrTypeObjectPos:
		case kAttrTypeWorldPos:
		case kAttrTypeViewPos:
		case kAttrTypeProjectionPos:
		case kAttrTypeScreenPos:
			{
				const char** suffixes = compoundAttrSuffixes( fType );
				MString      sChild;
				MObject      oaChildren[4];
				M_CHECK( fSize <= 4 );
				for ( int iChild = 0; iChild < fSize; ++iChild )
				{
					const char* suffix = suffixes[ iChild ];
					sChild = fName + suffix;
					oaChildren[ iChild ] = nAttr.create( sChild,
						sChild,
						MFnNumericData::kFloat,
						0.0,
						&status );
					M_CHECK( status );
				}

				if ( fSize == 4 )
				{
					oAttr2 = oaChildren[3];
					if ( fType == kAttrTypeColor3 ||
						fType == kAttrTypeColor4 ||
						fDescription.length() > 0 )
						nAttr.setKeyable( true );
				}

				oAttr = nAttr.create( fName,
					fName,
					oaChildren[0],
					oaChildren[1],
					oaChildren[2], 
					&status );
				M_CHECK( status );

				if ( fType == kAttrTypeColor3 ||
					fType == kAttrTypeColor4 )
				{
					nAttr.setKeyable( true );
					nAttr.setUsedAsColor( true );
				}
				else if ( fDescription.length() > 0 )
					nAttr.setKeyable( true );
				nAttr.setAffectsAppearance( true );

				break;
			}

		case kAttrTypeMatrix:
			// Create a generic matrix
			//
			oAttr = mAttr.create(fName, fName,
				MFnMatrixAttribute::kFloat, &status );
			M_CHECK( status );
			mAttr.setAffectsAppearance( true );
			break;

		case kAttrTypeWorldMatrix:
		case kAttrTypeViewMatrix:
		case kAttrTypeProjectionMatrix:
		case kAttrTypeWorldViewMatrix:
		case kAttrTypeWorldViewProjectionMatrix:
			// These matricies are handled internally and have no attribute.
			//
			break;

		case kAttrTypeColor1DTexture:
		case kAttrTypeColor2DTexture:
		case kAttrTypeColor3DTexture:
		case kAttrTypeColor2DRectTexture:
		case kAttrTypeNormalTexture:
		case kAttrTypeBumpTexture:
		case kAttrTypeCubeTexture:
		case kAttrTypeEnvTexture:
		case kAttrTypeNormalizationTexture:
			if( pNode->getTexturesByName())
			{
				oAttr = tAttr.create(fName, fName, MFnData::kString,
					MObject::kNullObj, &status );
				M_CHECK( status );
				tAttr.setAffectsAppearance( true );
			}
			else
			{
				const char* suffix1 = "R";
				const char* suffix2 = "G";
				const char* suffix3 = "B";

				MObject oChild1 = nAttr.create(fName + suffix1,
					fName + suffix1,
					MFnNumericData::kFloat,
					0.0, &status);
				MObject oChild2 = nAttr.create(fName + suffix2,
					fName + suffix2,
					MFnNumericData::kFloat,
					0.0, &status);

				MObject oChild3 = nAttr.create(fName + suffix3,
					fName + suffix3,
					MFnNumericData::kFloat,
					0.0, &status);

				oAttr = nAttr.create( fName,
					fName,
					oChild1,
					oChild2,
					oChild3, 
					&status );
				M_CHECK( status );

				// Although it's not strictly necessary, set this attribute
				// to be a color so the user can will at least get the
				// texture assignment button in the AE if for some reason
				// our AE template is missing
				nAttr.setUsedAsColor( true );

				nAttr.setAffectsAppearance( true );
			}
			break;
#ifdef _WIN32
		case kAttrTypeTime:
#endif
		case kAttrTypeOther:
			break;
		default:
			M_CHECK( false );
		}

		if (oAttr.isNull())
		{
			// There is no attribute for this parameter
			//
			return false;
		}

		// Add the attribute to the node
		//
		status = mod->addAttribute( oNode, oAttr );
		M_CHECK( status );

		if (!oAttr2.isNull())
		{
			status = mod->addAttribute( oNode, oAttr2 );
			M_CHECK( status );
		}

		// Hold onto a copy of the attribute for easy access later.
		fAttr  = oAttr;
		fAttr2 = oAttr2;

		// If we need to connect this node to some other node, do so.
		//
		if (doConnection)
		{
			status = mod->connect(oSrcNode, oSrcAttr, oDstNode, oDstAttr);
			M_CHECK( status );
		}
		return true;                   // success
	}
	catch ( cgfxShaderCommon::InternalError* e )   
	{
		size_t ee = (size_t)e;
		fType = kAttrTypeUnknown;
		MString sMsg = "(";
		sMsg += (int)ee;
		sMsg += ") cgfxShader node \"";
		sMsg += fnNode.name();
		sMsg += "\": unable to add attribute \"";
		sMsg += fName;
		sMsg += "\"";
		MGlobal::displayWarning( sMsg );
		return false;                  // failure
	}
	catch (...)
	{
		M_CHECK( false );
	}

	return true;
}                                      // cgfxAttrDef::createAttribute


bool
cgfxAttrDef::destroyAttribute( MObject& oNode, MDGModifier* dgMod)
{
	MStatus status;

	// If this is a texture node, clear the value (which will destroy
	// any attached textures)
	if( fType >= kAttrTypeFirstTexture && fType <= kAttrTypeLastTexture)
		setTexture( oNode, "", dgMod);

	// New effect won't need this old attr anymore.  
	status = dgMod->removeAttribute( oNode, fAttr );

	// If there is a secondary attribute, remove that too.
	if ( !fAttr2.isNull() )
		status = dgMod->removeAttribute( oNode, fAttr2 );

	// Don't leave dangling references to deleted attributes.
	fAttr  = MObject::kNullObj;  
	fAttr2 = MObject::kNullObj;  

	return status == MStatus::kSuccess;
}                                      // cgfxAttrDef::destroyAttribute




// ========== cgfxAttrDef::updateNode ==========
//
// This routine takes a node and an effect and ensures that all those
// and only those attributes that should be on the node, are on the
// node.
//
// The output cgfxAttrDefList and its elements are newly 
// allocated.
//
/* static */
void
cgfxAttrDef::updateNode(
    const cgfxRCPtr<const cgfxEffect>&  effect,         // IN
    cgfxShaderNode*                     pNode,          // IN
    MDGModifier*                        dgMod,          // UPD
    cgfxRCPtr<cgfxAttrDefList>&         effectList,     // OUT
    MStringArray&                       attributeList ) // OUT
{
	MStatus status;

	effectList = cgfxRCPtr<cgfxAttrDefList>();

	try
	{
		MObject           oNode = pNode->thisMObject();
		MFnDependencyNode fnNode( oNode );
		MFnAttribute      fnAttr;

		effectList = effect->attrsFromEffect();  // caller will own this list
        cgfxRCPtr<cgfxAttrDefList> nodeList = attrsFromNode( oNode ); // oNode owns this one

		cgfxAttrDefList::iterator emIt;
		cgfxAttrDefList::iterator nmIt;

		cgfxAttrDef* adef;

		// Walk through the nodeList.  Delete each attribute that is not
		// also found in the effect list.
		//
		for (nmIt = nodeList->begin(); nmIt; ++nmIt)
		{                              // loop over nodeList
			adef = (*nmIt);

			// Skip if node doesn't have this attribute.
			if ( adef->fAttr.isNull() )
				continue;

			// Look for a matching attribute in the effect
			emIt = effectList->find( adef->fName );

			// Drop Maya attribute from node if shader var
			//   was declared in old effect, but not declared
			//   in new effect, or data type is not the same.
			if ( !emIt ||
				(*emIt)->fType != adef->fType )
			{
				adef->destroyAttribute( oNode, dgMod);
			}

			// If this is a texture and it has a non-default
			// value, we should switch to this mode of texture
			// definition (names or nodes)
			else if( emIt && 
				(*emIt)->fType >= kAttrTypeFirstTexture &&
				(*emIt)->fType <= kAttrTypeLastTexture)
			{
				// Get the attribute type of the existing attribute
				// and ensure our node is setup to digest the correct
				// type of textures
				MFnTypedAttribute typedFn( adef->fAttr);
				bool usesName = typedFn.attrType() == MFnData::kString;
				pNode->setTexturesByName( usesName);

				// Finally, if this texture uses fileTexture nodes then
				// mark the value as "tweaked" to prevent the default value 
				// code from trying to attach a default fileTexture node. 
				// This is crucial to being able to load shaders back in. The
				// Maya file will create our node using the following steps:
				//	1) Create an empty cgfxShader node
				//	2) Create all the dynamic attributes (like this texture)
				//	3) Set the effect attribute (which calls this code)
				//	4) Create DG connections to file texture nodes
				// So, if we did setup a default file texture node, the load
				// would be unable to connect the real file texture. 
				//
				if( !usesName)
					(*emIt)->fTweaked = true;
			}


		}                              // loop over nodeList

		// Delete any unnecessary attributes before starting to add new ones
		// (in case we're deleting and re-creating a property with a different
		// type)
		// 
		dgMod->doIt();

		// Walk through the effectList.  Add each item that is not also
		// found in the node list.
		//
		for (emIt = effectList->begin(); emIt; ++emIt)
		{                              // loop over effectList 
			adef = (*emIt);

			// Note: nodeList::find will work with a null this pointer
			// so nodeList->find() will work even if nodeList is NULL.
			//
			nmIt = nodeList->find( adef->fName );

			// Double check that the attr still exists.  Get current value.
			cgfxAttrDef* cdef = NULL;
			if ( nmIt &&
				!(*nmIt)->fAttr.isNull() )
				cdef = attrFromNode( fnNode,
				(*nmIt)->fName, 
				(*nmIt)->fType,
			   	(*nmIt)->fDescription,
				(*nmIt)->fSemantic);

			// Add new Maya attribute.
			if ( !cdef )
				adef->createAttribute( oNode, dgMod, pNode );

			// Go on with existing attribute.
			else
			{                          // use existing attribute
				// Copy the attribute handles to the new effect's cgfxAttrDef.
				adef->fAttr  = (*nmIt)->fAttr;
				adef->fAttr2 = (*nmIt)->fAttr2;

				// Did we already notice that the user has set this attr?
				if ( (*nmIt)->fTweaked )
					adef->fTweaked = true;

				// If no old effect, then the current values were
				//   loaded from the scene file, and should override 
				//   the new effect's defaults if they are different.
				else if ( pNode->effect().isNull() )
				{
					if ( !adef->isInitialValueEqual( *cdef ) )
						adef->fTweaked = true;
				}

				// If current value is not the same as old effect's 
				//   default, then user has adjusted it.  Current value
				//   takes precedence over the new effect's default.
				else if ( !(*nmIt)->isInitialValueEqual( *cdef ) )
					adef->fTweaked = true;

				// User hasn't changed this value.  New effect's
				// default takes precedence.  Since we are going to
				// change the value, remember to change it back 
				// to the old effect's default in case of undo.
				// Among other things, this logic allows the UI shader
				// description to update as different effects are chosen.
				else
					(*nmIt)->fInitOnUndo = true;

				delete cdef;
			}                          // use existing attribute
		}                              // loop over effectList 

		// Now rebuild the attributeList attribute value.  This is an array
		// of strings of the format "attrName<TAB>type<TAB>Description<TAB>Semantic".
		//
		MString tmpStr;

		attributeList.clear();

		cgfxAttrDefList::iterator it(effectList);

		while (it)
		{
			cgfxAttrDef* aDef = *it;

			tmpStr = aDef->fName;
			tmpStr += "\t";
			tmpStr += (int)aDef->fType;
			tmpStr += "\t";
			tmpStr += aDef->fDescription;
			tmpStr += "\t";
			tmpStr += aDef->fSemantic;

			// Drop trailing tabs.
			const char* bp = tmpStr.asChar();
			const char* ep;
			for ( ep = bp + tmpStr.length(); bp < ep; --ep )
				if ( ep[-1] != '\t' )
					break;

			attributeList.append( MString( bp, (int)(ep - bp) ) );
			++it;
		}
	}
	catch ( cgfxShaderCommon::InternalError* )   
	{
		throw;
	}
	catch (...)
	{
		M_CHECK( false );
	}
}                                      // cgfxAttrDef::updateNode


// Return true if initial value of 'this' is same as 'that'.
bool
cgfxAttrDef::isInitialValueEqual( const cgfxAttrDef& that ) const
{
	if ( fStringDef != that.fStringDef )
		return false;

	const double* thisNumericDef = fNumericDef;
	const double* thatNumericDef = that.fNumericDef;

	if ( thisNumericDef == thatNumericDef ) 
		return true;

	// Make sure we don't proceed to test default colour values for
	// texture attributes as the colour itself is meaningless!
	//
	if( fType == that.fType && fType >= kAttrTypeFirstTexture && fType <= kAttrTypeLastTexture)
		return true;

	if ( !thisNumericDef )
	{
		thisNumericDef = thatNumericDef;
		thatNumericDef = fNumericDef;
	}

	if ( !thatNumericDef )
	{
		if ( fType == kAttrTypeMatrix )
		{
			thatNumericDef = &MMatrix::identity.matrix[0][0];
			M_CHECK( fSize == 16 && that.fSize == 16 );
		}
		else
		{
			static const double d0[4] = {0.0, 0.0, 0.0, 0.0};
			thatNumericDef = d0;
			M_CHECK( fSize <= sizeof(d0)/sizeof(d0[0]) );
			if ( fSize != that.fSize )
				MGlobal::displayWarning( "CgFX attribute size mismatch" );
		}
	}

	double eps = 0.0001;
	int i;
	for ( i = 0; i < fSize; ++i )
		if ( thisNumericDef[ i ] + eps < thatNumericDef[ i ] ||
			thatNumericDef[ i ] + eps < thisNumericDef[ i ] )
			return false;

	return true;
}                                      // cgfxAttrDef::isInitialValueEqual


// Copy initial value from given attribute.
void
cgfxAttrDef::setInitialValue( const cgfxAttrDef& from )
{
	if ( from.fNumericDef )
	{
		M_CHECK( fSize == from.fSize );
		if ( !fNumericDef )
			fNumericDef = new double[ fSize ];
		memcpy( fNumericDef, from.fNumericDef, fSize * sizeof( *fNumericDef ) );
	}
	else
	{
		delete fNumericDef;
		fStringDef = from.fStringDef;
	}
}                                      // cgfxAttrDef::setInitialValue


// Set attribute flag
void cgfxAttrDef::setAttributeFlags()
{
	MFnAttribute attribute;
	if(!attribute.setObject(fAttr))
		return ;

	switch (fType)
	{
	case kAttrTypeColor3:
	case kAttrTypeColor4:
		attribute.setKeyable( true );
		attribute.setUsedAsColor( true );
		attribute.setAffectsAppearance( true );
		break;

	case kAttrTypeBool:
	case kAttrTypeInt:
	case kAttrTypeFloat:
		attribute.setKeyable( true );
		attribute.setAffectsAppearance( true );
		break;
	
	case kAttrTypeVector2:
	case kAttrTypeVector3:
	case kAttrTypeVector4:
	case kAttrTypeObjectDir:
	case kAttrTypeWorldDir:
	case kAttrTypeViewDir:
	case kAttrTypeProjectionDir:
	case kAttrTypeScreenDir:
	case kAttrTypeObjectPos:
	case kAttrTypeWorldPos:
	case kAttrTypeViewPos:
	case kAttrTypeProjectionPos:
	case kAttrTypeScreenPos:
		if(	fDescription.length() > 0)
			attribute.setKeyable( true );
		attribute.setAffectsAppearance( true );
		break;

	case kAttrTypeString:
	case kAttrTypeMatrix: // Create a generic matrix
		attribute.setAffectsAppearance( true );
		break;

	case kAttrTypeWorldMatrix:
	case kAttrTypeViewMatrix:
	case kAttrTypeProjectionMatrix:
	case kAttrTypeWorldViewMatrix:
	case kAttrTypeWorldViewProjectionMatrix:
		// These matricies are handled internally and have no attribute.
		//
		break;

	case kAttrTypeColor1DTexture:
	case kAttrTypeColor2DTexture:
	case kAttrTypeColor3DTexture:
	case kAttrTypeColor2DRectTexture:
	case kAttrTypeNormalTexture:
	case kAttrTypeBumpTexture:
	case kAttrTypeCubeTexture:
	case kAttrTypeEnvTexture:
	case kAttrTypeNormalizationTexture:
		/*if(!getTexturesByName())
			// Although it's not strictly necessary, set this attribute
			// to be a color so the user can will at least get the
			// texture assignment button in the AE if for some reason
			// our AE template is missing
			attribute.setUsedAsColor( true );*/
		attribute.setAffectsAppearance( true );
		break;

#ifdef _WIN32
	case kAttrTypeTime:
#endif
	case kAttrTypeOther:
		break;
	default:
		M_CHECK( false );
	}
}

// Set Maya attributes to their initial values.
/* static */
void
cgfxAttrDef::initializeAttributes(
    MObject&                            oNode,
    const cgfxRCPtr<cgfxAttrDefList>&   list,
    bool                                bUndoing,
    MDGModifier*                        dgMod)
{
	MStatus             status;
	MFnMatrixAttribute  fnMatrix;
	MFnNumericAttribute fnNumeric;
	MFnTypedAttribute   fnTyped;
	for ( cgfxAttrDefList::iterator it( list ); it; ++it )
	{                                  // loop over cgfxAttrDefList
		cgfxAttrDef* aDef = (*it);

		if ( aDef->fAttr.isNull() )    // if no Maya attr
			continue;                  // try next

		bool bSetValue = bUndoing ? aDef->fInitOnUndo
			: !aDef->fTweaked;

		try
		{
			// Boolean
			if ( aDef->fType == kAttrTypeBool )
			{
				if ( bSetValue )
					aDef->setValue( oNode, aDef->fNumericDef && aDef->fNumericDef[0] );
			}

			// Texture node: must be check before both numeric (as a
			// texture could be a float3 colour) and string (as it
			// could also be a string)
			else if( aDef->fType >= kAttrTypeFirstTexture && 
				aDef->fType <= kAttrTypeLastTexture)
			{
				if ( bSetValue )
					aDef->setTexture( oNode, aDef->fStringDef, dgMod);
			}

			// Numeric
			else if ( fnNumeric.setObject( aDef->fAttr ) ) 
			{                          // numeric attr

				// Constants for removing old bounds...
				double vMin = -FLT_MAX;
				double vMax = FLT_MAX;
				if ( aDef->fType == kAttrTypeInt )
				{
					vMin = INT_MIN;
					vMax = INT_MAX;
				}

				// Set or remove bounds.

				switch ( aDef->fSize )
				{                      // switch to set/remove bounds
				case 1:
					if ( aDef->fNumericMin )
						fnNumeric.setMin( aDef->fNumericMin[0] );
					else if ( fnNumeric.hasMin() )
						fnNumeric.setMin( vMin );

					if ( aDef->fNumericMax )
						fnNumeric.setMax( aDef->fNumericMax[0] );
					else if ( fnNumeric.hasMax() )
						fnNumeric.setMax( vMax );

					if ( aDef->fNumericSoftMin )
						fnNumeric.setSoftMin( aDef->fNumericSoftMin[0] );
					else if ( fnNumeric.hasSoftMin() )
						fnNumeric.setSoftMin( vMin );

					if ( aDef->fNumericSoftMax )
						fnNumeric.setSoftMax( aDef->fNumericSoftMax[0] );
					else if ( fnNumeric.hasSoftMax() )
						fnNumeric.setSoftMax( vMax );
					break;

				case 2:
					if ( aDef->fNumericMin )
						fnNumeric.setMin( aDef->fNumericMin[0], 
						aDef->fNumericMin[1] );
					else if ( fnNumeric.hasMin() )
						fnNumeric.setMin( vMin, vMin );

					if ( aDef->fNumericMax )
						fnNumeric.setMax( aDef->fNumericMax[0], 
						aDef->fNumericMax[1] );
					else if ( fnNumeric.hasMax() )
						fnNumeric.setMax( vMax, vMax );
					break;

				case 3:
				case 4:
					if ( aDef->fNumericMin )
						fnNumeric.setMin( aDef->fNumericMin[0], 
						aDef->fNumericMin[1],
						aDef->fNumericMin[2] );
					else if ( fnNumeric.hasMin() )
						fnNumeric.setMin( vMin, vMin, vMin );

					if ( aDef->fNumericMax )
						fnNumeric.setMax( aDef->fNumericMax[0], 
						aDef->fNumericMax[1],
						aDef->fNumericMax[2] );
					else if ( fnNumeric.hasMax() )
						fnNumeric.setMax( vMax, vMax, vMax );
					break;

				default:
					M_CHECK( false );
				}                      // switch to set/remove bounds

				// Set initial value.
				//   Use 0 if no initial value specified in .fx file.
				if ( bSetValue )
				{                      // set numeric initial value
					static const double d0[4] = {0.0, 0.0, 0.0, 0.0};
					const double* pNumericDef = aDef->fNumericDef ? aDef->fNumericDef
						: d0;
					switch ( aDef->fSize )
					{                  
					case 1:
						if ( aDef->fType == kAttrTypeInt )
							aDef->setValue( oNode, (int)pNumericDef[0] );
						else
							aDef->setValue( oNode, (float)pNumericDef[0] );
						break;

					case 2:
						aDef->setValue( oNode,
							(float)pNumericDef[0],
							(float)pNumericDef[1] );
						break;

					case 3:
						aDef->setValue( oNode,
							(float)pNumericDef[0],
							(float)pNumericDef[1],
							(float)pNumericDef[2] );
						break;

					case 4:
						status = fnNumeric.setObject( aDef->fAttr2 );
						M_CHECK( status );

						if ( aDef->fNumericMin )
							fnNumeric.setMin( aDef->fNumericMin[3] );
						else if ( fnNumeric.hasMin() )
							fnNumeric.setMin( vMin );

						if ( aDef->fNumericMax )
							fnNumeric.setMax( aDef->fNumericMax[3] );
						else if ( fnNumeric.hasMax() )
							fnNumeric.setMax( vMax );

						aDef->setValue( oNode,
							(float)pNumericDef[0],
							(float)pNumericDef[1],
							(float)pNumericDef[2],
							(float)pNumericDef[3] );
						break;

					default:
						M_CHECK( false );
					}                  // switch ( aDef->fSize )
				}                      // set numeric initial value
			}                          // numeric attr

			// String
			else if ( fnTyped.setObject( aDef->fAttr ) &&
				fnTyped.attrType() == MFnData::kString )
			{
				if ( bSetValue )
					aDef->setValue( oNode, aDef->fStringDef );
			}

			// Matrix
			else if ( fnMatrix.setObject( aDef->fAttr ) )
			{
				if ( !bSetValue )
				{}
				else if ( aDef->fNumericDef )
				{
					MMatrix m;
					double* p = &m.matrix[0][0];
					for ( int k = 0; k < 16; ++k )
						p[ k ] = aDef->fNumericDef[ k ];
					aDef->setValue( oNode, m );
				}
				else
					aDef->setValue( oNode, MMatrix::identity );
			}
		}
		catch ( cgfxShaderCommon::InternalError* e )   
		{
			size_t ee = (size_t)e;
			MFnDependencyNode fnNode( oNode );
			MString sMsg = "(";
			sMsg += (int)ee;
			sMsg += ") cgfxShader node \"";
			sMsg += fnNode.name();
			sMsg += "\": unable to initialize attribute \"";
			sMsg += aDef->fName;
			sMsg += "\"";
			MGlobal::displayWarning( sMsg );
		}
	}                                  // loop over cgfxAttrDefList
}                                      // cgfxAttrDef::initializeAttributes



// Clear all Maya attribute references in a cgfxAttrDefList.
//   This should be called whenever the list is detached from 
//   the cgfxShader node, to avert an eventual exception in 
//   MObject::~MObject() in case the referenced Maya attribute 
//   happens to be deleted while the cgfxAttrDefList is in
//   suspense on Maya's undo queue.
//
/* static */
void cgfxAttrDef::purgeMObjectCache(const cgfxRCPtr<cgfxAttrDefList>& list)
{                                       
	cgfxAttrDefList::iterator it( list );
	for ( ; it; ++it )
	{
		cgfxAttrDef* aDef = (*it);
		aDef->fAttr  = MObject::kNullObj;
		aDef->fAttr2 = MObject::kNullObj;
	}
}                                      // cgfxAttrDef::purgeMObjectCache


// Refresh Maya attribute references in a cgfxAttrDefList.
//   This should be called whenever a saved list is re-attached
//   to the cgfxShader node in the course of undo or redo.
//
/* static */
void cgfxAttrDef::validateMObjectCache(
    const MObject&                      obCgfxShader, 
    const cgfxRCPtr<cgfxAttrDefList>&   list)
{                                       
	MStatus status;
	MString sName2;
	MFnDependencyNode fnNode( obCgfxShader, &status );
	cgfxAttrDefList::iterator it( list );
	for ( ; it; ++it )
	{
		cgfxAttrDef* aDef = (*it);
		aDef->fAttr = fnNode.attribute( aDef->fName, &status );

		// 4-element vectors use an extra attribute for the 4th element.
		const char* suffix = aDef->getExtraAttrSuffix();
		if ( suffix )
			aDef->fAttr2 = fnNode.attribute( aDef->fName + suffix, &status );
	}
}                                      // cgfxAttrDef::validateMObjectCache


// Return suffix for Color4/Vector4 extra attribute, or NULL.
const char*
cgfxAttrDef::getExtraAttrSuffix() const
{
	if ( fSize == 4 )
		return compoundAttrSuffixes( fType )[ 3 ];
	return NULL;
}                                      // cgfxAttrDef::getExtraAttrSuffix


// Get a string representation of a cgfxAttrType
//
/* static */
const char* cgfxAttrDef::typeName( cgfxAttrType type )
{
#define CASE(name) case kAttrType##name: return #name

	switch (type)
	{
	default:    // Fall through into case unknown
		CASE(Unknown);
		CASE(Bool);
		CASE(Int);
		CASE(Float);
		CASE(String);
		CASE(Vector2);
		CASE(Vector3);
		CASE(Vector4);
		CASE(ObjectDir);
		CASE(WorldDir);
		CASE(ViewDir);
		CASE(ProjectionDir);
		CASE(ScreenDir);
		CASE(ObjectPos);
		CASE(WorldPos);
		CASE(ViewPos);
		CASE(ProjectionPos);
		CASE(ScreenPos);
		CASE(Color3);
		CASE(Color4);
		CASE(Matrix);
		CASE(WorldMatrix);
		CASE(ViewMatrix);
		CASE(ProjectionMatrix);
		CASE(WorldViewMatrix);
		CASE(WorldViewProjectionMatrix);
		CASE(Color1DTexture);
		CASE(Color2DTexture);
		CASE(Color3DTexture);
		CASE(Color2DRectTexture);
		CASE(NormalTexture);
		CASE(BumpTexture);
		CASE(CubeTexture);
		CASE(EnvTexture);
		CASE(NormalizationTexture);
#ifdef _WIN32
		CASE(Time);
#endif
		CASE(Other);
	}
}


/* static */
const char**
cgfxAttrDef::compoundAttrSuffixes( cgfxAttrType eAttrType )
{
	static const char* simple[] = { NULL, NULL, NULL, NULL,    NULL };
	static const char* vector[] = { "X",  "Y",  "Z",  "W",     NULL };
	static const char* color[]  = { "R",  "G",  "B",  "Alpha", NULL };

	const char** p;
	switch ( eAttrType )
	{
	case kAttrTypeVector2:
	case kAttrTypeVector3:
	case kAttrTypeVector4:
	case kAttrTypeObjectDir:
	case kAttrTypeWorldDir:
	case kAttrTypeViewDir:
	case kAttrTypeProjectionDir:
	case kAttrTypeScreenDir:
	case kAttrTypeObjectPos:
	case kAttrTypeWorldPos:
	case kAttrTypeViewPos:
	case kAttrTypeProjectionPos:
	case kAttrTypeScreenPos:
		p = vector;
		break;
	case kAttrTypeColor3:
	case kAttrTypeColor4:
		p = color;
		break;
	default:
		p = simple;
		break;
	}
	return p;
}                                      // cgfxAttrDef::compoundAttrSuffixes



// Methods to get attribute values
//
void
cgfxAttrDef::getValue( MObject& oNode, bool& value ) const
{
	MStatus status;
	MPlug plug(oNode, fAttr);
	status = plug.getValue(value);
	M_CHECK( status );
}

void
cgfxAttrDef::getValue( MObject& oNode, int& value ) const
{
	MStatus status;
	MPlug plug(oNode, fAttr);
	status = plug.getValue(value);
	M_CHECK( status );
}

void
cgfxAttrDef::getValue( MObject& oNode, float& value ) const
{
	MStatus status;
	MPlug plug(oNode, fAttr);
	status = plug.getValue(value);
	if( fUnits != MDistance::kInvalid)
	{
		value = (float)MDistance( value, fUnits).as( MDistance::internalUnit());
	}
	M_CHECK( status );
}

void
cgfxAttrDef::getValue( MObject& oNode, MString& value ) const
{
	MStatus status;
	MPlug plug(oNode, fAttr);
	status = plug.getValue(value);
	M_CHECK( status );
}

void
cgfxAttrDef::getValue( MObject& oNode, float& v1, float& v2 ) const
{
	MStatus status;
	MPlug plug(oNode, fAttr);

	MObject oData;
	status = plug.getValue(oData);
	M_CHECK( status );

	MFnNumericData fnData(oData, &status);
	M_CHECK( status );

	status = fnData.getData(v1, v2);
	M_CHECK( status );

	if( fUnits != MDistance::kInvalid)
	{
		v1 = (float)MDistance( v1, fUnits).as( MDistance::internalUnit());
		v2 = (float)MDistance( v2, fUnits).as( MDistance::internalUnit());
	}
}

void
cgfxAttrDef::getValue( MObject& oNode,
											float& v1, float& v2, float& v3 ) const
{
	MStatus status;
	MPlug plug(oNode, fAttr);

	MObject oData;
	status = plug.getValue(oData);
	M_CHECK( status );

	MFnNumericData fnData(oData, &status);
	M_CHECK( status );

	status = fnData.getData(v1, v2, v3);
	M_CHECK( status );

	if( fUnits != MDistance::kInvalid)
	{
		v1 = (float)MDistance( v1, fUnits).as( MDistance::internalUnit());
		v2 = (float)MDistance( v2, fUnits).as( MDistance::internalUnit());
		v3 = (float)MDistance( v3, fUnits).as( MDistance::internalUnit());
	}
}

void
cgfxAttrDef::getValue( MObject& oNode,
											float& v1, float& v2, float& v3, float& v4 ) const
{
	MStatus status;
	MPlug plug(oNode, fAttr);
	MPlug plug2(oNode, fAttr2);

	MObject oData;
	status = plug.getValue(oData);
	M_CHECK( status );

	MFnNumericData fnData(oData, &status);
	M_CHECK( status );

	status = fnData.getData(v1, v2, v3);
	M_CHECK( status );

	// Get the 4th value from the extra attribute.
	//
	status = plug2.getValue(v4);
	M_CHECK( status );

	if( fUnits != MDistance::kInvalid)
	{
		v1 = (float)MDistance( v1, fUnits).as( MDistance::internalUnit());
		v2 = (float)MDistance( v2, fUnits).as( MDistance::internalUnit());
		v3 = (float)MDistance( v3, fUnits).as( MDistance::internalUnit());
		v4 = (float)MDistance( v4, fUnits).as( MDistance::internalUnit());
	}
}

void
cgfxAttrDef::getValue( MObject& oNode, MMatrix& value ) const
{
	MStatus status;
	MPlug plug(oNode, fAttr);

	MObject oData;
	status = plug.getValue(oData);
	M_CHECK( status );

	MFnMatrixData fnData(oData, &status);
	M_CHECK( status );

	value = fnData.matrix(&status);
	M_CHECK( status );
}

void
cgfxAttrDef::getValue( MObject& oNode, MImage& value ) const
{
	MStatus status = MS::kFailure;
	MPlug plug(oNode, fAttr);

	if (fType >= kAttrTypeFirstTexture &&
		fType <= kAttrTypeLastTexture)
	{
		MPlugArray plugArray;
		plug.connectedTo(plugArray, true, false, &status);
		M_CHECK( status );

		if (plugArray.length() != 1)
			M_CHECK( status );

		MPlug srcPlug = plugArray[0];
		MObject oSrcNode = srcPlug.node();

		// OutputDebugStrings("Source texture object = ", oSrcNode.apiTypeStr());

		value.release();
		status = value.readFromTextureNode(oSrcNode);
		M_CHECK( status );
	}
	M_CHECK( status );
}


// Get the source of an attribute value
//
void
cgfxAttrDef::getSource( MObject& oNode, MPlug& src) const
{
	MStatus status = MS::kFailure;
	MPlug plug(oNode, fAttr);

	MPlugArray plugArray;
	plug.connectedTo(plugArray, true, false, &status);
	M_CHECK( status && plugArray.length() <= 1);

	if (plugArray.length() == 1)
		src = plugArray[0];
}


// Methods to set attribute values
//
void
cgfxAttrDef::setValue( MObject& oNode, bool value )
{
	MStatus status;
	MPlug plug(oNode, fAttr);
	status = plug.setValue(value);
	M_CHECK( status );
}

void
cgfxAttrDef::setValue( MObject& oNode, int value )
{
	MStatus status;
	MPlug plug(oNode, fAttr);
	status = plug.setValue(value);
	M_CHECK( status );
}

void
cgfxAttrDef::setValue( MObject& oNode, float value )
{
	MStatus status;
	MPlug plug(oNode, fAttr);
	status = plug.setValue(value);
	M_CHECK( status );
}

void
cgfxAttrDef::setValue( MObject& oNode, const MString& value )
{
	MStatus status;
	MPlug plug(oNode, fAttr);
	status = plug.setValue((MString &)value);
	M_CHECK( status );
}

void
cgfxAttrDef::setValue( MObject& oNode, float v1, float v2 )
{
	MStatus status;
	MPlug plug(oNode, fAttr);

	MFnNumericData fnData;
	MObject oData = fnData.create(MFnNumericData::k2Float, &status);
	M_CHECK( status );

	fnData.setData(v1, v2);
	status = plug.setValue(oData);
	M_CHECK( status );
}

void
cgfxAttrDef::setValue( MObject& oNode, float v1, float v2, float v3 )
{
	MStatus status;
	MPlug plug(oNode, fAttr);

	MFnNumericData fnData;
	MObject oData = fnData.create(MFnNumericData::k3Float, &status);
	M_CHECK( status );

	fnData.setData(v1, v2, v3);
	status = plug.setValue(oData);
	M_CHECK( status );
}

void
cgfxAttrDef::setValue( MObject& oNode, float v1, float v2, float v3, float v4 )
{
	MStatus status;
	MPlug plug(oNode, fAttr);
	MPlug plug2(oNode, fAttr2);

	MFnNumericData fnData;
	MObject oData = fnData.create(MFnNumericData::k3Float, &status);
	M_CHECK( status );

	fnData.setData(v1, v2, v3);
	status = plug.setValue(oData);
	M_CHECK( status );

	status = plug2.setValue(v4);
	M_CHECK( status );
}

void
cgfxAttrDef::setValue( MObject& oNode, const MMatrix& v )
{
	MStatus       status;
	MFnMatrixData fnData;
	MObject       oData = fnData.create( v, &status );
	M_CHECK( status );

	MPlug plug( oNode, fAttr );
	status = plug.setValue( oData );
	M_CHECK( status );
}

// Utility to check if a node is used by any nodes other than us
//
bool isUsedElsewhere( MObject node, MObject user)
{
	for( MItDependencyGraph iter( node); !iter.isDone(); iter.next())
	{
		// If there is a downstream connection to something other than our shader ...
		//
		if( iter.thisNode() != node)
		{
			if( iter.thisNode() != user)
			{
				// And that connection uses anything other than the message attribute
				// of the texture (which is used to connect the texture to the 
				// global texture list)
				//
				MPlugArray src;
				iter.thisPlug().connectedTo( src, true, false);
				if( src.length() == 1 && src[ 0].partialName() != "msg")
				{
					// Finally, check this isn't just the swatch renderer taking
					// a quick look at this node
					//
					MFnDependencyNode dgFn( iter.thisNode());
					if( dgFn.name() != "swatchShadingGroup")
					{
						// Then we're not the only user of this node!
						//
						//cout<<"Not removing node due to connection<<src[0].name().asChar()<<" to "<<iter.thisPlug().name().asChar()<<endl;
						return true;
					}
				}
			}

			// If this downstream connection is to another shader, or a
			// message connection, don't follow the connection any further
			//
			iter.prune();
		}
	}
	return false;
}


void
cgfxAttrDef::setTexture( MObject& oNode, const MString& value, MDGModifier*	dgMod)
{
	MStatus status;
	MPlug plug(oNode, fAttr);

	// Is this a node or name based texture?
	//
	MFnAttribute attrFn( fAttr);
	if( attrFn.isUsedAsColor() )
	{
		// Node based texture.
		// Remove any existing texture
		//
		if( plug.isConnected())
		{
			MPlugArray src;
			plug.connectedTo( src, true, false, &status);
			M_CHECK( status );
			if( src.length() > 0)
			{
				MObject textureNode = src[ 0].node();

				// If no other nodes use this texture, we can remove it to 
				// avoid cluttering up the scene with unused texture nodes
				//
				if( !isUsedElsewhere( textureNode, oNode))
				{
					// We are the only user of this texture node so we
					// can delete it. Before we do that though, are
					// we the only user of the placement node too?
					//
					MFnDependencyNode textureFn( textureNode);
					MPlug uvPlug = textureFn.findPlug( "uv", &status);
					if( status == MS::kSuccess)
					{
						MPlugArray placementNode;
						uvPlug.connectedTo( placementNode, true, false, &status);
						M_CHECK( status );

						// If we are the only user of the placement node, delete 
						// it as well
						//
						if( placementNode.length() > 0 &&
							!isUsedElsewhere( placementNode[ 0].node(), textureNode))
							M_CHECK( dgMod->deleteNode( placementNode[ 0].node()) );
					}

					// Delete the texture node
					//
					M_CHECK( dgMod->deleteNode( textureNode) );
				}
				else
				{
					// We're not deleting the texture, so just disconnect it
					//
					M_CHECK( dgMod->disconnect( src[ 0], plug) );
				}
			}
		}

		// Do we have a (default) value to set?
		//
		if( value.length() > 0)
		{
			// Resolve the texture value as either an absolute or
			// project relative path. Even though we re-resolve paths
			// when loading textures ourselves, if we want the Maya
			// texture swatch to display, Maya needs to be able to
			// find the texture as well.
			//
			MString relativePath = cgfxFindFile( value, true );

			// If we didn't find it, just leave the original path (even
			// though it wont work)
			//
			if( relativePath.length() == 0) relativePath = value;

			// Create a new file texture and placement node
			// Use the MEL commands (as opposed to dgMod.createNode) as these
			// correctly hook up the rendering message connections so our 
			// nodes show up in the hypershade etc.
			//
			MObject textureNode, placementNode;
			MSelectionList originalSelection, newlyCreatedNode;
			MGlobal::getActiveSelectionList( originalSelection);
			MGlobal::executeCommand( "shadingNode -asTexture file", false, true);
			MGlobal::getActiveSelectionList( newlyCreatedNode);
			M_CHECK( newlyCreatedNode.length() > 0 && newlyCreatedNode.getDependNode( 0, textureNode));
			MGlobal::executeCommand( "shadingNode -asUtility place2dTexture", false, true);
			MGlobal::getActiveSelectionList( newlyCreatedNode);
			M_CHECK( newlyCreatedNode.length() > 0 && newlyCreatedNode.getDependNode( 0, placementNode));
			MGlobal::setActiveSelectionList( originalSelection);
			MFnDependencyNode fileTextureFn( textureNode, &status);
			M_CHECK( status );
			MFnDependencyNode placementFn( placementNode, &status);
			M_CHECK( status );

			// Connect the placement node to the file texture node
			//
			M_CHECK( dgMod->connect( placementFn.findPlug( "coverage"), fileTextureFn.findPlug( "coverage")) );
			M_CHECK( dgMod->connect( placementFn.findPlug( "translateFrame"), fileTextureFn.findPlug( "translateFrame")) );
			M_CHECK( dgMod->connect( placementFn.findPlug( "rotateFrame"), fileTextureFn.findPlug( "rotateFrame")) );
			M_CHECK( dgMod->connect( placementFn.findPlug( "mirrorU"), fileTextureFn.findPlug( "mirrorU")) );
			M_CHECK( dgMod->connect( placementFn.findPlug( "mirrorV"), fileTextureFn.findPlug( "mirrorV")) );
			M_CHECK( dgMod->connect( placementFn.findPlug( "stagger"), fileTextureFn.findPlug( "stagger")) );
			M_CHECK( dgMod->connect( placementFn.findPlug( "wrapU"), fileTextureFn.findPlug( "wrapU")) );
			M_CHECK( dgMod->connect( placementFn.findPlug( "wrapV"), fileTextureFn.findPlug( "wrapV")) );
			M_CHECK( dgMod->connect( placementFn.findPlug( "repeatUV"), fileTextureFn.findPlug( "repeatUV")) );
			M_CHECK( dgMod->connect( placementFn.findPlug( "offset"), fileTextureFn.findPlug( "offset")) );
			M_CHECK( dgMod->connect( placementFn.findPlug( "rotateUV"), fileTextureFn.findPlug( "rotateUV")) );
			M_CHECK( dgMod->connect( placementFn.findPlug( "noiseUV"), fileTextureFn.findPlug( "noiseUV")) );
			M_CHECK( dgMod->connect( placementFn.findPlug( "vertexUvOne"), fileTextureFn.findPlug( "vertexUvOne")) );
			M_CHECK( dgMod->connect( placementFn.findPlug( "vertexUvTwo"), fileTextureFn.findPlug( "vertexUvTwo")) );
			M_CHECK( dgMod->connect( placementFn.findPlug( "vertexUvThree"), fileTextureFn.findPlug( "vertexUvThree")) );
			M_CHECK( dgMod->connect( placementFn.findPlug( "vertexCameraOne"), fileTextureFn.findPlug( "vertexCameraOne")) );
			M_CHECK( dgMod->connect( placementFn.findPlug( "outUV"), fileTextureFn.findPlug( "uv")) );
			M_CHECK( dgMod->connect( placementFn.findPlug( "outUvFilterSize"), fileTextureFn.findPlug( "uvFilterSize")) );

			// Connect our file texture node to our shader attribute, then set the texture
			//
			M_CHECK( dgMod->connect( fileTextureFn.findPlug( "outColor"), plug) );
			status = fileTextureFn.findPlug( "fileTextureName").setValue( relativePath);
			M_CHECK( status );
		}

		M_CHECK( dgMod->doIt() );
	}
	else
	{
		// Simple String attribute - but do a safety check to be sure
		//
		MFnTypedAttribute fnTyped;
		if( fnTyped.setObject( fAttr ) &&
			fnTyped.attrType() == MFnData::kString )
		{
			status = plug.setValue((MString &)value);
			M_CHECK( status );
		}
	}
}


//--------------------------------------------------------------------//
//                          cgfxAttrDefList                           //
//--------------------------------------------------------------------//

cgfxAttrDefList::iterator
cgfxAttrDefList::findInsensitive( const MString& name )
{
	const char* pName = name.asChar();
	unsigned    lName = name.length();
	iterator    it( *this );
	for ( ; it; ++it )
		if ( lName == (*it)->fName.length() &&
			0 == stricmp( pName, (*it)->fName.asChar() ) )
			break;
	return it;
};                                     // cgfxAttrDefList::findInsensitive

void cgfxAttrDefList::release()
{
	--refcount;
	if (refcount <= 0)
	{
        M_CHECK( refcount == 0 );
		delete this;
	}
};

void cgfxAttrDefList::releaseTextures()
{
    iterator it(*this);
    while (it)
    {
        (*it)->release();
        ++it;
    }
}

void cgfxAttrDefList::dump(const char* name)
{
    fprintf(stderr, "Dumping cgfxAttrDefList %s : \n", name);
  
    for (cgfxAttrDefList::iterator it = begin(); it; ++it) {
        cgfxAttrDef* aDef = (*it);
        fprintf(
            stderr,
            "   name=%s, type=%s, size=%d, attr=%s, hint=%d, attr2=%s, def=%s, desc=%s, semantic=%s, CGParameter=0x%p\n",
            aDef->fName.asChar(),
            cgfxAttrDef::typeName(aDef->fType),
            aDef->fSize,
            aDef->fAttr.apiTypeStr(),
            aDef->fHint,
            aDef->fAttr2.apiTypeStr(),
            aDef->fStringDef.asChar(),
            aDef->fDescription.asChar(),
            aDef->fSemantic.asChar(),
            aDef->fParameterHandle
        );
    }
}
